#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "daemon.hpp"

enum 
{
    UsageError=1,
    SocketError,
    BindError,
    ListenError
};

class TcpServer;

class ThreadData 
{
public:
    ThreadData(int fd,const std::string &ip,const uint16_t &port,TcpServer *t)
        :sockfd(fd),clientip(ip),clientport(port),tsvr(t)
    {}

public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

const int defaultfd=-1;
const std::string defaultip="0.0.0.0";
const int backlog=10;
extern Log lg;

class TcpServer
{
public:
    TcpServer(const uint16_t &port,const std::string &ip=defaultip)
        :_listensock(defaultfd),_port(port),_ip(ip)
    {}

    ~TcpServer()
    {}

    void Init()
    {
        Socket();
        Bind();
        Listen();
    }

    static void *Routine(void *args) //线程执行函数
    {
        pthread_detach(pthread_self()); //设置为分离状态就不用线程等待了
        ThreadData *td=static_cast<ThreadData*>(args);
        td->tsvr->Service(td->sockfd,td->clientip,td->clientport);
        delete td;
        return nullptr;
    }

    void Start()
    {
        Daemon();
        ThreadPool<Task>::GetInstance()->Start();
        lg(Info,"tcpserver is running...");
        for(;;)
        {
            //1.获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);            

            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip)); 

            int clientsock=0;
            while(!Accept(client,clientsock));

            //2.根据新连接来进行通信
            lg(Info, "get a new link..., sockfd: %d, \
            client ip: %s, client port: %d",clientsock, clientip, clientport);            
            
            //1.单进程版本
            //因为是单进程的，所以在执行完Service函数，才会执行上面的代码
            //否则就会阻塞在这里
            //Service(clientsock,clientip,clientport);
            //close(clientsock);

            //2.多进程版本
            //可以实现多进程的并行访问了，但是资源开销太高
            /*
            pid_t id=fork();
            if(id==0) //子进程拷贝父进程文件描述符
            {
                close(_listensock); //关闭父进程的监听套接字
                //释放子进程本身，这样父进程就可以执行pid_t rid=waitpid(id,nullptr,0);
                if(fork()>0) exit(0); 

                //child执行我们的任务
                Service(clientsock,clientip,clientport); //孙子进程，init system领养
                close(clientsock); //关闭子进程文件描述符
                exit(0);
            }
            close(clientsock); //关闭父进程文件描述符
            //father继续获取新连接
            pid_t rid=waitpid(id,nullptr,0);
            (void)rid;
            */

           //3.多线程版本
           //减少了资源的销毁，但是如果连接的用户过多，多线程也会越来越多
           //ThreadData *td=new ThreadData(clientsock,clientip,clientport,this);
           //pthread_t tid; 
           //pthread_create(&tid,nullptr,Routine,td); //获取了一个新连接，将套接字给这个线程

            //4.线程池版本
            Task t(clientsock,clientip,clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

    //创建套接字
    void Socket()
    {
        _listensock=socket(AF_INET,SOCK_STREAM,0);
        if(_listensock<0)
        {
            lg(Fatal,"create socket, errno: %d, errstring: %s",errno,strerror(errno));
            exit(SocketError);
        }
        lg(Info,"create socket success, sock: %d",_listensock);
    
        //前台进程才可以使用键盘文件，后台进程不能从标准输入获取数据 
        int opt = 1; //防止偶发性的服务器无法进行立即重启
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt)); 
    }

    //绑定套接字
    void Bind()
    {
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));

        local.sin_family=AF_INET;
        local.sin_port=htons(_port);
        local.sin_addr.s_addr=INADDR_ANY;
        //inet_aton(ip_.c_str(), &(local.sin_addr));

        if(bind(_listensock,(struct sockaddr*)&local, sizeof(local))<0)
        {
            lg(Fatal,"bind error, error: %d, errstring: %s",errno,strerror(errno));
            exit(BindError);
        }
        lg(Info,"bind socket success, _listensock: %d",_listensock);
    }

    //监听套接字
    void Listen()
    {
        if(listen(_listensock,backlog)<0) //backlog表示套接字等待的最大连接数
        {
            lg(Fatal,"listen error, error: %d, errstring: %s",errno,strerror(errno));
            exit(ListenError);            
        }
        lg(Info,"listen socket success, _listensock: %d",_listensock);
    }

    //接受套接字
    bool Accept(struct sockaddr_in& client,int& clientsock)
    {
        socklen_t len = sizeof(client);           
        int sockfd = accept(_listensock, (struct sockaddr *)&client, &len);
        if (sockfd < 0)
        {
            lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); 
            //continue;
            return false;
        }
        clientsock=sockfd;
        return true;
    }

    //单进程测试代码
    void Service(int clientsock,const std::string& clientip,const uint16_t& clientport)
    {
        char buffer[4096];
        while(true)
        {
            ssize_t n=read(clientsock,buffer,sizeof(buffer)); //从套接字中读取数据
            if(n>0)
            {
                buffer[n]=0;
                std::cout<<"client say# "<<buffer<<std::endl;
                std::string echo_string ="tcpserver echo# ";
                echo_string+=buffer;
            
                write(clientsock,echo_string.c_str(),echo_string.size()); //向client中写入数据
            }
            else if(n==0)
            {
                lg(Info,"%s:%d quit, server close socket: %d",clientip.c_str(),clientport,clientsock);
                break;
            }
            else 
            {
                lg(Warning,"read error, sockfd: %d, client ip: %s, client port: %d",clientsock,clientip.c_str(),clientport);
                break;
            }   
        }
    }

private:
    int _listensock; //监听套接字
    uint16_t _port; //端口号
    std::string _ip; //ip地址
};